/*
 * GECRStandings.java
 * Created on 2 févr. 2008
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2008, Jens and Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.data.dao.standings;

import java.math.BigDecimal;
import java.math.RoundingMode;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.hockeo.vhbase.data.dao.IGEntity;
import com.hockeo.vhbase.data.dao.club.GClub;
import com.hockeo.vhbase.data.dao.season.GSeason;
import com.hockeo.vhbase.data.helper.GStandingsRankTendency;

/**
 * Represents one standings record for the European Club Ranking (ECR). There is one
 * record for each season and club if the club has obtained points during one of the last
 * five seasons.
 *
 * @version $Id$
 * @author jjanke
 */
@Entity
@Table( name = "h_ecr_standings" )
public class GECRStandings implements Comparable<GECRStandings>, IGEntity
{
  @Id
  @Column( name = "id" )
  @GeneratedValue( strategy = GenerationType.AUTO )
  private int                    d_nId;

  @ManyToOne( fetch = FetchType.LAZY )
  @JoinColumn( name = "season_id" )
  private GSeason                d_season;

  @ManyToOne( fetch = FetchType.LAZY )
  @JoinColumn( name = "club_id" )
  private GClub                  d_club;

  /**
   * Total number of ECR points (sum of points from S1 to S5).
   */
  @Column( name = "points_total" )
  private BigDecimal             d_decPointsTotal          = new BigDecimal( "0.00" );

  @Column( name = "rank" )
  private int                    d_nRank                   = 1;

  @Column( name = "rank_prev" )
  private int                    d_nPrevRank               = 0;

  @Column( name = "rank_diff" )
  private int                    d_nRankDiff               = 0;

  @Enumerated( EnumType.STRING )
  @Column( name = "rank_tendency" )
  private GStandingsRankTendency d_rankTendency            = GStandingsRankTendency.UNCHANGED;

  /**
   * 100% of the sum of all ECR points (league, cup, European competitions) from the
   * current season.
   */
  @Column( name = "points_s1" )
  private BigDecimal             d_decPointsS1             = new BigDecimal( "0.00" );

  /**
   * 80% of all ECR points from the previous season.
   */
  @Column( name = "points_s2" )
  private BigDecimal             d_decPointsS2             = new BigDecimal( "0.00" );

  /**
   * 60% of all ECR points from the season two years ago.
   */
  @Column( name = "points_s3" )
  private BigDecimal             d_decPointsS3             = new BigDecimal( "0.00" );

  /**
   * 40% of all ECR points from the season three years ago.
   */
  @Column( name = "points_s4" )
  private BigDecimal             d_decPointsS4             = new BigDecimal( "0.00" );

  /**
   * 20% of all ECR points from the season four years ago.
   */
  @Column( name = "points_s5" )
  private BigDecimal             d_decPointsS5             = new BigDecimal( "0.00" );

  /**
   * The number of points that are lost in the next season. A loss is due to the fact that
   * the 5th last season will no longer be considered and all other season devalorized.
   * I.e. the amount in this variable indicates how many points the team must earn in the
   * next season to keep its number of points on the same level.
   */
  @Column( name = "points_expiring" )
  private BigDecimal             d_decPointsExpiringNextSeason = new BigDecimal( "0.00" );

  /**
   * ECR points for last season's league competition.
   */
  @Column( name = "points_s1_league" )
  private BigDecimal             d_decPointsS1League       = new BigDecimal( "0.00" );

  /**
   * ECR points for last season's national cup competition.
   */
  @Column( name = "points_s1_cup" )
  private BigDecimal             d_decPointsS1Cup          = new BigDecimal( "0.00" );

  /**
   * ECR points for last season's top European club competition.
   */
  @Column( name = "points_s1_eu1" )
  private BigDecimal             d_decPointsS1EU1          = new BigDecimal( "0.00" );

  /**
   * ECR points for last season's secondary European club competition.
   */
  @Column( name = "points_s1_eu2" )
  private BigDecimal             d_decPointsS1EU2          = new BigDecimal( "0.00" );

  /** Default parameterless constructor. */
  protected GECRStandings()
  {}

  /**
   * Creates a new ECR standings object from scratch. This constructor must be used if
   * there is no standings object for the specified club in the previous season.
   *
   * @param club the club for which to build an ECR standings
   * @param season the season for which to build an ECR standings object
   */
  public GECRStandings( GClub club, GSeason season )
  {
    d_club = club;
    d_season = season;
  }

  /**
   * Creates a new ECR standings object from scratch. This constructor must be used if
   * there is a standings object for the specified club in the previous season.
   *
   * @param prevStandings the previous season's standings
   * @param season the season for which to build an ECR standings object
   */
  public GECRStandings( GECRStandings prevStandings, GSeason season )
  {
    this( prevStandings.getClub(), season );

    d_nPrevRank = prevStandings.getRank();

    // S1 from previous standings goes to S2 (from 100% to 80%)
    //
    if ( prevStandings.getPointsS1().compareTo( BigDecimal.valueOf( 0 ) ) > 0 )
      d_decPointsS2 = prevStandings.getPointsS1().divide( BigDecimal.valueOf( 5L ) ).multiply( BigDecimal.valueOf( 4L ) ).setScale(
          2, RoundingMode.HALF_UP );

    // S2 from previous standings goes to S3 (from 80% to 60%)
    //
    if ( prevStandings.getPointsS2().compareTo( BigDecimal.valueOf( 0 ) ) > 0 )
      d_decPointsS3 = prevStandings.getPointsS2().divide( BigDecimal.valueOf( 4L ) ).multiply( BigDecimal.valueOf( 3L ) ).setScale(
          2, RoundingMode.HALF_UP );

    // S3 from previous standings goes to S4 (from 60% to 40%)
    //
    if ( prevStandings.getPointsS2().compareTo( BigDecimal.valueOf( 0 ) ) > 0 )
      d_decPointsS4 = prevStandings.getPointsS3().divide( BigDecimal.valueOf( 3L ) ).multiply( BigDecimal.valueOf( 2L ) ).setScale(
          2, RoundingMode.HALF_UP );

    // S4 from previous standings goes to S5 (from 40% to 20%)
    //
    if ( prevStandings.getPointsS2().compareTo( BigDecimal.valueOf( 0 ) ) > 0 )
      d_decPointsS5 = prevStandings.getPointsS4().divide( BigDecimal.valueOf( 2L ), 2, RoundingMode.HALF_UP );
  }

  /**
   * Sets the rank of this standings object and updates the improvement tendency if there
   * has been an ECR standings object for last year.
   *
   * @param nRank the rank to be attributed to these standings
   */
  public void setRank( int nRank )
  {
    d_nRank = nRank;

    // There is no tendency to update if only points for the last season are registered
    //
    if ( d_decPointsS2.compareTo( BigDecimal.valueOf( 0L ) ) > 0
        || d_decPointsS3.compareTo( BigDecimal.valueOf( 0L ) ) > 0
        || d_decPointsS4.compareTo( BigDecimal.valueOf( 0L ) ) > 0
        || d_decPointsS5.compareTo( BigDecimal.valueOf( 0L ) ) > 0 )
    {
      if ( d_nRank < d_nPrevRank )
        d_rankTendency = GStandingsRankTendency.IMPROVED;
      else if ( d_nRank > d_nPrevRank )
        d_rankTendency = GStandingsRankTendency.DEGRADED;
      else
        d_rankTendency = GStandingsRankTendency.UNCHANGED;

      d_nRankDiff = d_nPrevRank - d_nRank;
    }
  }

  public GSeason getSeason()
  {
    return d_season;
  }

  public GClub getClub()
  {
    return d_club;
  }

  public BigDecimal getPointsTotal()
  {
    return d_decPointsTotal;
  }

  public int getRank()
  {
    return d_nRank;
  }

  public int getPrevRank()
  {
    return d_nPrevRank;
  }

  public int getRankDiff()
  {
    return d_nRankDiff;
  }

  public GStandingsRankTendency getRankTendency()
  {
    return d_rankTendency;
  }

  public BigDecimal getPointsS1()
  {
    return d_decPointsS1;
  }

  public BigDecimal getPointsS2()
  {
    return d_decPointsS2;
  }

  public BigDecimal getPointsS3()
  {
    return d_decPointsS3;
  }

  public BigDecimal getPointsS4()
  {
    return d_decPointsS4;
  }

  public BigDecimal getPointsS5()
  {
    return d_decPointsS5;
  }

  public BigDecimal getPointsExpiringNextSeason()
  {
    return d_decPointsExpiringNextSeason;
  }

  public BigDecimal getPointsS1League()
  {
    return d_decPointsS1League;
  }

  public BigDecimal getPointsS1Cup()
  {
    return d_decPointsS1Cup;
  }

  public BigDecimal getPointsS1EU1()
  {
    return d_decPointsS1EU1;
  }

  public BigDecimal getPointsS1EU2()
  {
    return d_decPointsS1EU2;
  }

  /**
   * @param decPoints points obtained in last year's league
   */
  public void setPointsS1League( BigDecimal decPoints )
  {
    d_decPointsS1League = decPoints;
  }

  /**
   * @param decPoints points obtained in last year's national cup
   */
  public void setPointsS1Cup( BigDecimal decPoints )
  {
    d_decPointsS1Cup = decPoints;
  }

  /**
   * @param decPoints points obtained in last year's top-level European contest
   */
  public void setPointsS1EU1( BigDecimal decPoints )
  {
    d_decPointsS1EU1 = decPoints;
  }

  /**
   * @param decPoints points obtained in last year's second-level European contest
   */
  public void setPointsS1EU2( BigDecimal decPoints )
  {
    d_decPointsS1EU2 = decPoints;
  }

  /**
   * Calculates the number of points for the last season (S1) and recalculates the points
   * total.
   */
  public void updateTotals()
  {
    d_decPointsS1 = d_decPointsS1League.add( d_decPointsS1Cup ).add( d_decPointsS1EU1 ).add( d_decPointsS1EU2 );
    d_decPointsTotal = d_decPointsS1.add( d_decPointsS2 ).add( d_decPointsS3 ).add( d_decPointsS4 ).add( d_decPointsS5 );

    // recalculate how many points will be lost next season
    //
    BigDecimal decTmp;

    // All points will be lost from the 5th last season
    //
    d_decPointsExpiringNextSeason = d_decPointsS5;

    // Half of the points of the 4th last season will be lost (from 40% to 20%)
    //
    if ( d_decPointsS4.compareTo( BigDecimal.ZERO ) > 0 )
    {
      decTmp = d_decPointsS4.divide( BigDecimal.valueOf( 2L ), 2, RoundingMode.HALF_UP );
      d_decPointsExpiringNextSeason = d_decPointsExpiringNextSeason.add( decTmp );
    }

    // One third of the points of the 3rd last season will be lost (from 60% to 40%)
    //
    if ( d_decPointsS3.compareTo( BigDecimal.ZERO ) > 0 )
    {
      decTmp = d_decPointsS3.divide( BigDecimal.valueOf( 3L ), 2, RoundingMode.HALF_UP );
      d_decPointsExpiringNextSeason = d_decPointsExpiringNextSeason.add( decTmp );
    }

    // One quarter of the points of the 2nd last season will be lost (from 80% to 60%)
    //
    if ( d_decPointsS2.compareTo( BigDecimal.ZERO ) > 0 )
    {
      decTmp = d_decPointsS2.divide( BigDecimal.valueOf( 4L ), 2, RoundingMode.HALF_UP );
      d_decPointsExpiringNextSeason = d_decPointsExpiringNextSeason.add( decTmp );
    }

    // One fifth of the points of the last season will be lost (from 100% to 80%)
    //
    if ( d_decPointsS1.compareTo( BigDecimal.ZERO ) > 0 )
    {
      decTmp = d_decPointsS1.divide( BigDecimal.valueOf( 5L ), 2, RoundingMode.HALF_UP );
      d_decPointsExpiringNextSeason = d_decPointsExpiringNextSeason.add( decTmp );
    }
  }

  public int getId()
  {
    return d_nId;
  }

  public Class<? extends IGEntity> getGEntityClass()
  {
    return GECRStandings.class;
  }

  public boolean instanceOf( Class<? extends IGEntity> classOther )
  {
    return classOther.isAssignableFrom( getGEntityClass() );
  }

  /**
   * Compares two ECR objects according to their inversed natural ordering which is
   * established by the following criteria:
   *
   * 1) Total number of points. 2) Number of points for last season. 3) Number of points
   * for 2nd last season etc.
   *
   * The ordering is reversed! I.e. if this object is greater, a negative number is
   * returned.
   *
   * @see java.lang.Comparable#compareTo(java.lang.Object)
   */
  public int compareTo( GECRStandings ecrOther )
  {
    int nCmp;

    nCmp = ecrOther.getPointsTotal().compareTo( getPointsTotal() );
    if ( nCmp != 0 )
      return nCmp;

    nCmp = ecrOther.getPointsS1().compareTo( getPointsS1() );
    if ( nCmp != 0 )
      return nCmp;

    nCmp = ecrOther.getPointsS2().compareTo( getPointsS2() );
    if ( nCmp != 0 )
      return nCmp;

    nCmp = ecrOther.getPointsS3().compareTo( getPointsS3() );
    if ( nCmp != 0 )
      return nCmp;

    nCmp = ecrOther.getPointsS4().compareTo( getPointsS4() );
    if ( nCmp != 0 )
      return nCmp;

    return ecrOther.getPointsS5().compareTo( getPointsS5() );
  }

  /*
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals( Object obj )
  {
    try
    {
      return d_nId == ( (GECRStandings) obj ).getId();
    }
    catch ( Exception ex ) // especially ClassCastException(s) may happen
    {
      return false;
    }
  }

  /*
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode()
  {
    return d_nId;
  }

  /*
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString()
  {
    return "ECR standings " + d_nId + " for " + d_club.getName() + " and season: " + d_season.getName() + " (points: "
        + d_decPointsTotal.toString() + ")";
  }
}
